Skip to content

fix(proxy): forward content-frame API calls + errors to chrome shell - #2

Closed
andylbrummer wants to merge 2 commits into
mainfrom
fix/proxy-ui-content-shell-bridge
Closed

andylbrummer wants to merge 2 commits into
mainfrom
fix/proxy-ui-content-shell-bridge

Conversation

@andylbrummer

Copy link
Copy Markdown
Member

Problem

The proxy UI tabs (Network, Errors) and their badges stopped updating with the page's real API calls and front-end errors.

Root cause: the always-wrap shell model (injector.go BuildShellDocument, default for top-level navigations) renders a page as a chrome shell wrapping the real page in a content <iframe>. The indicator UI runs in the shell frame, but:

  • api-tracker.js wraps fetch/XHR per-frame → app calls land in the content frame's callBuffer.
  • core.js error capture runs only in the content frame (__isContent gate at init) → JS/console errors land in the content frame's buffers.

The shell's own buffers stayed empty, so getCalls() / getDeduplicatedErrors() / getStats() — which feed the tabs + badges via the 1s poll — saw nothing.

Fix

Content frames forward each captured call/error up to the shell via the existing same-origin cross-frame idiom (frames.js window.parent.__devtool_*):

  • api-tracker.js — addCall forwards to shell.__devtool_api_ingest when role==='content'; new terminal ingest sink; chrome stops recording its own WS/screenshot fetches as Network noise.
  • core.js — forwardErrorToShell + ingestForwardedError; forward at the js/console/warn buffer pushes; window.__devtool_errors_ingest export.

Shell ingest pushes into the shell's own buffers, so getCalls / getDeduplicatedErrors / getStats / audits all work unchanged. No loop (shell is terminal). Backward compatible — standalone (no-shell) pages keep local capture; forward is gated on role==='content'.

Verification

Opt-in headless-Chrome regression test (bridge_live_test.go, AGNT_LIVE_BRIDGE=1). A content-frame fetch + uncaught throw + console.error all surface in the shell frame:

{"role":"chrome","urls":["…/api/data"],"stats":{"jsErrorCount":1,"consoleErrorCount":1,"totalCount":2}}

Normal suite green (go test ./internal/proxy/...); live test skips unless gated.

🤖 Generated with Claude Code

andylbrummer and others added 2 commits June 27, 2026 18:33
The always-wrap shell model renders a top-level navigation as a chrome
shell wrapping the real page in a content <iframe>. The indicator UI runs
in the shell, but fetch/XHR capture (api-tracker.js) and error capture
(core.js, content-only) happen in the content frame. The shell's own
buffers stayed empty, so the Network and Errors tabs (and their badges)
never updated with the page's real traffic.

Content frames now forward each captured call/error up to the shell via
same-origin window.parent.__devtool_*_ingest (the existing frames.js
cross-frame idiom). Shell-side terminal ingest sinks push into the shell's
own buffers, so getCalls / getDeduplicatedErrors / getStats / audits all
work unchanged. Chrome stops recording its own WS/screenshot traffic as
Network noise. Standalone (no-shell) pages keep local capture — forward
is gated on role==='content'.

Verified end-to-end in headless Chrome (AGNT_LIVE_BRIDGE=1): a content
fetch + uncaught throw + console.error all surface in the shell frame's
buffers.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HMry6m6QXTdc8Xbecqmzid
Document that shell-side ingestForwardedError intentionally skips the
consolidated error stream — harmless today (no shell consumer reads
getConsolidated*), but a future consumer must wire it in.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HMry6m6QXTdc8Xbecqmzid
andylbrummer pushed a commit that referenced this pull request Jul 10, 2026
…t lines (G3)

Adds a structured-parser layer over the G2 catch-all (internal/overlay):
recognized multi-line/noisy error blocks fold into a single
{kind, message, file:line} alert instead of surfacing as several `unparsed`
lines. Closes DoD #2 of the get_errors epic.

- structured_parsers.go: StructuredError + Compact(); parsePrisma (folds the
  `prisma:error` header + `Invalid \`prisma.x.y()\` invocation` banner + bare
  file:line call-site + cause into one line, extracting op + file:line + a
  remediation hint); parseDBAuth (raw pg/mysql `password authentication failed`,
  `database "x" does not exist`, `too many connections`). Registry +
  runStructuredParsers + isStructuralPrefix. Anchors informed by R1
  (docs/error-formats/prisma-orm-sql.md).
- alerts.go: ProcessLine runs structured parsers on unclassified lines BEFORE
  the catch-all; structural-prefix lines (block header/banner) are folded at
  the cause line rather than surfaced standalone; adds recentSnapshot().
- ECONNREFUSED intentionally left to the existing `connection-refused` pattern;
  stack frames are already non-surfaced (don't trip the catch-all).

Prisma DB-auth block now renders as one compact line carrying the cause +
prisma.user.findUnique() @ /app/src/db.ts:42 — measurably shorter than the raw
block (asserted). G1 catch-all contract preserved (block still surfaces, now
structured). testing-review passed.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
andylbrummer pushed a commit that referenced this pull request Jul 10, 2026
…lking)

Replaced the hand-rolled map[string]interface{} walking in all 8
StatusFetcher.fetch* methods with typed DTOs (status_dto.go) decoded via
json.Unmarshal. Each per-field `m["x"].(string)` / `.(float64)` access is gone;
fetchers now decode into a json-tagged DTO and convert with a small toInfo().

This eliminates the brittle pattern that caused the earlier empty-event-log bug
(fetchStartupLog silently missing a field/key): adding or renaming a status
field is now a typed DTO change in one place instead of matching string keys by
hand across map walks. Behavior is unchanged — same daemon calls, same display
structs out; the overlay tests pass.

Note: this is the safe, type-safety half of the "#2" overview work. The
larger LOC collapse (8 fetches -> 1 batched OVERVIEW verb) was intentionally
NOT done: it would force the daemon to re-derive per-proxy log/page data
in-process (divergence risk) for no real latency gain now that the heavy
per-tick scans are cached. Net LOC here is ~flat; the win is type safety and
single-site field edits, not line count.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@andylbrummer
andylbrummer force-pushed the fix/proxy-ui-content-shell-bridge branch from 7aacd8d to 8502aeb Compare July 10, 2026 22:57
@andylbrummer
andylbrummer deleted the fix/proxy-ui-content-shell-bridge branch July 11, 2026 04:44
andylbrummer pushed a commit that referenced this pull request Jul 18, 2026
Five operator-reported rough edges surfaced from a live session:

- monitor firehose (#3): a single page load emitted per-node DOM mutations
  by the hundred, blowing the downstream consumer's rate limit. Add a
  mutationCoalescer that folds a same-target burst into one rolling
  "[mutation] N changes (M nodes)" summary (500ms / target-change / 50-cap
  buckets, tail-flush on disconnect). Compact stream only; JSON stays 1:1.

- raw gzip in compact log (#4): formatCompact/formatJSON dumped ResponseBody
  raw, but the traffic recorder captures on-the-wire bytes that stay
  compressed for any response not decompressed for HTML injection. New
  httpBodyPreview renders "<gzip-encoded, N bytes>" / "<binary, N bytes>"
  instead of garbage.

- misleading exec-timeout diagnostic (#5): the hub timeout message echoed the
  raw frame arg (usually ""), reading as "no frame" even when the exec was
  routed to the active content frame. New ProxyServer.DescribeExecTarget makes
  the message report the resolved destination plus a heavy-screenshot hint.

- access-URL typo (#6): listenAddr is a full host:port, so "http://localhost"
  + listenAddr produced "http://localhost127.0.0.1:47341". Extract testable
  proxyAccessURL; 0.0.0.0 now renders "http://<your-ip>:PORT".

- daemon start help (#6): the command blocks in the foreground but the help
  text implied auto-start/detach. Spell out that it holds the terminal until
  Ctrl+C and does not detach.

Also determinize TestPageTracker_SessionTimeout, which flaked in the
pre-commit race suite (unrelated to the above): its Active flag is recomputed
against wall-clock now on each read, so a single fixed 5ms sleep can race the
scheduler on an oversubscribed host. Poll for the post-timeout invariant with
generous headroom instead. Ref .claude/rules/testing-timing-assertion-flakes.md.

(Reported item #2 — watch emitting --target — was already fixed; the tool
emits --types. Verified against the built binary, no change needed.)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
andylbrummer pushed a commit that referenced this pull request Jul 19, 2026
Replaces the three LOCAL vendored process-lifecycle patches (agnt commits
77a98d2/613c74e9, b964cb2/e74fad05, 3804dda) with released upstream code
now that they merged and shipped as go-cli-server v0.5.7:

- PR #1 recycled-PID cleanup guard (carry scanner-captured identities through
  post-Wait cleanup instead of re-sampling a possibly-reused PID/PGID)
- PR #2 serialize spawn vs shutdown for StateStarting procs (startMu-ordered
  final-check/spawn/publish; ctx-bounded Shutdown wait)
- PR #3 negative-PID signal only a verified stable group leader

No go.mod replace directive. Vendored tree matches the released v0.5.7 tag
exactly. agnt's own internal/platform/* changes stay local (not upstreamed).

Verified: go vet, GOOS=windows cross-build, and full 'go test -p 1 ./...'
serial suite all green.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014Rk1F3dUJLaL6f9tqu5fRA
andylbrummer pushed a commit that referenced this pull request Aug 20, 2026
…gnals

The recorder's fixed +1200ms settle after __devtool appears is flaky under
CPU load (too short) and wasteful when idle (too long). Pin the replacement
to the ACTUAL signals a live bundle exposes — indicator/toast mounted AND the
websocket transport OPEN — so the gate keys on the connection being live, not
on a timer that fires before it (lessons-ssh-transport.md #2). Predicates are
not yet exported: RED.

worktrack-task: 01M08GG2XSD0PT8RRA8GC9K2RE
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant